#include "mxpbasic.h"

uint8_t key_buf[250] = {0};
uint8_t key_pos = 0;

int16_t token_num = 0;
uint8_t token_str[128];
uint8_t last_char = 0;
uint8_t last_token = TOKEN_NONE;

uint16_t code_len = 0;
uint8_t line_buf[250];

uint8_t running = 0;
uint16_t code_line = 0;
uint16_t jmp_line = 0;
uint8_t code_pos = 0;

int16_t vars[26];
int16_t stack[128];
uint8_t tos;

//#include "stdio.h"
// uint8_t getkey() { return getchar(); }
// void putkey(uint8_t c) {
//  putchar(c);
//  fflush(stdout);
//}
// uint8_t code[1024];

#define HasChar() (*((uint8_t *)0xfff0))
#define GetChar() (*((uint8_t *)0xfff1))
#define PutChar(c) (*((uint8_t *)0xfff2) = c)
uint8_t getkey() {
  uint8_t c;
  while (!HasChar())
    ;
  c = GetChar();
  PutChar(c);
  return c;
}
void putkey(uint8_t c) { PutChar(c); }
uint8_t *code = (uint8_t *)0xC000;

// static uint8_t *input = "10 PRINT 123\n"
//                        "20 END\n"
//                        "11 PRINT 789\n"
//                        "LIST\n"
//                        "10 PRINT 1\n"
//                        "LIST\n"
//                        "10 PRINT 456\n"
//                        "LIST\n"
//                        "10\n"
//                        "LIST\n"
//                        "10 PRINT 789\n"
//                        "LIST\n";
// static uint8_t *input = "10 LET I=0\n"
//                        "11 LET N=0\n"
//                        "20 LET N = N+I\n"
//                        "30 LET I=I+1\n"
//                        "31 PRINT I, N\n"
//                        "40 IF I <= 10 THEN GOTO 20\n"
//                        "50 PRINT N\n"
//                        "60 END\n"
//                        "RUN\n";
// static uint8_t *input = "10 GOSUB 50\n"
//                        "20 PRINT 123\n"
//                        "30 END\n"
//                        "50 PRINT 456\n"
//                        "60 RETURN\n"
//                        "RUN\n";
// uint8_t io_getchar() {
//  if (*input) {
//    putchar_(*input);
//    return *input++;
//  }
//  while (1)
//    ;
//}

void io_reset() { key_buf[key_pos] = 0; }

uint8_t io_getchar() {
  uint8_t c = key_buf[key_pos];
  if (c == 0) {
    key_pos = 0;
    while (1) {
      c = getkey();

      if (c == '\b') {
        if (key_pos > 0) {
          key_pos--;
        }
        continue;
      }

      if (c == '\n') {
        key_buf[key_pos++] = '\n';
        key_buf[key_pos] = 0;
        key_pos = 0;
        break;
      }

      if (c >= 32 && c < 127) {
        key_buf[key_pos++] = c;
      }
    }
  }

  return key_buf[key_pos++];
}
#define io_putchar putkey

void putstr(uint8_t *str) {
  while (*str) {
    io_putchar(*str++);
  }
}

void putnum_u(uint16_t num) {
  if (num > 9) {
    putnum_u(num / 10);
  }
  io_putchar(num % 10 + '0');
}

void putnum(int16_t num) {
  if (num < 0) {
    io_putchar('-');
    putnum_u(-num);
  } else {
    putnum_u(num);
  }
}

void syntax_error() {
  uint8_t c;

  running = 0;

  putstr("[SYNTAX ERROR]\n");

  // skip to line end
  do {
    c = next_char();
  } while (c != '\n');
}

uint16_t str_len(uint8_t *str) {
  uint16_t len = 0;
  while (*str) {
    len++;
    str++;
  }
  return len;
}

uint8_t str_eq(uint8_t *str1, uint8_t *str2) {
  uint8_t c1;
  uint8_t c2;
  while (1) {
    c1 = *str1;
    c2 = *str2;

    if (c1 != c2) {
      return 0;
    }
    if (c1 == 0 && c2 == 0) {
      return 1;
    }
    str1++;
    str2++;
  }
}

void run_error() {
  running = 0;
  putstr("[RUN ERROR] Line:");
  putnum(code_line);
  putstr("\n");
}

uint8_t next_code_char() {
  uint8_t c = code[code_pos];
  if (c == 0) {
    return '\n';
  }
  code_pos++;
  return c;
}

uint8_t next_char() {
  uint8_t c;

  if (last_char) {
    c = last_char;
    last_char = 0;
  } else {
    if (running) {
      c = next_code_char();
    } else {
      c = io_getchar();
    }
  }

  return c;
}

uint8_t next_token() {
  uint8_t token = TOKEN_NONE;
  uint8_t c;
  uint8_t n = 0;

  if (last_token != TOKEN_NONE) {
    token = last_token;
    last_token = TOKEN_NONE;
    return token;
  }

  // skip space
  do {
    c = next_char();
  } while (c == ' ' || c == '\t');

  if (c >= '0' && c <= '9') {
    token_num = 0;
    do {
      token_str[n++] = c;
      token_num = token_num * 10 + c - '0';
      c = next_char();
    } while (c >= '0' && c <= '9');
    last_char = c;
    token_str[n] = 0;

    return TOKEN_NUM;
  }

  if (c == '"') {
    token_str[n++] = '"';
    while (1) {
      c = next_char();
      if (c == '"') {
        token_str[n++] = '"';
        token_str[n] = 0;
        return TOKEN_STR;
      } else if (c == '\n') {
        syntax_error();
        return TOKEN_SYNTAX;
      }
      token_str[n++] = c;
    }
  }

  if (c == ',') {
    token_str[n++] = ',';
    token_str[n] = 0;
    return TOKEN_COMMA;
  }

  if (c == '\n') {
    token_str[n++] = '\n';
    token_str[n] = 0;
    return TOKEN_EOL;
  }

  if (c == '(') {
    token_str[n++] = '(';
    token_str[n] = 0;
    return TOKEN_LP;
  }

  if (c == ')') {
    token_str[n++] = ')';
    token_str[n] = 0;
    return TOKEN_RP;
  }

  if (c == '+') {
    token_str[n++] = '+';
    token_str[n] = 0;
    return TOKEN_ADD;
  }

  if (c == '-') {
    token_str[n++] = '-';
    token_str[n] = 0;
    return TOKEN_SUB;
  }

  if (c == '*') {
    token_str[n++] = '*';
    token_str[n] = 0;
    return TOKEN_MUL;
  }

  if (c == '/') {
    token_str[n++] = '/';
    token_str[n] = 0;
    return TOKEN_DIV;
  }

  if (c == '=') {
    token_str[n++] = '=';
    token_str[n] = 0;
    return TOKEN_EQ;
  }

  if (c == '>') {
    token_str[n++] = '>';
    c = next_char();
    if (c == '=') {
      token_str[n++] = '=';
      token_str[n] = 0;
      return TOKEN_GE;
    } else {
      token_str[n] = 0;
      last_char = c;
      return TOKEN_GT;
    }
  }

  if (c == '<') {
    token_str[n++] = '<';
    c = next_char();
    if (c == '=') {
      token_str[n++] = '=';
      token_str[n] = 0;
      return TOKEN_LE;
    } else if (c == '>') {
      token_str[n++] = '>';
      token_str[n] = 0;
      return TOKEN_NEQ;
    } else {
      token_str[n] = 0;
      last_char = c;
      return TOKEN_LT;
    }
  }

  // 转大写
  while (1) {
    if (c >= 'a' && c <= 'z') {
      c = c - 0x20;
      token_str[n++] = c;
    } else if (c >= 'A' && c <= 'Z') {
      token_str[n++] = c;
    } else {
      last_char = c;
      token_str[n] = 0;
      break;
    }
    c = next_char();
  }

  if (n == 1) {
    token_num = token_str[0] - 'A';
    return TOKEN_VAR;
  }

  if (str_eq("PRINT", token_str)) {
    return TOKEN_PRINT;
  }

  if (str_eq("IF", token_str)) {
    return TOKEN_IF;
  }

  if (str_eq("THEN", token_str)) {
    return TOKEN_THEN;
  }

  if (str_eq("GOTO", token_str)) {
    return TOKEN_GOTO;
  }

  if (str_eq("LET", token_str)) {
    return TOKEN_LET;
  }

  if (str_eq("GOSUB", token_str)) {
    return TOKEN_GOSUB;
  }

  if (str_eq("RETURN", token_str)) {
    return TOKEN_RETURN;
  }

  if (str_eq("INPUT", token_str)) {
    return TOKEN_INPUT;
  }

  if (str_eq("END", token_str)) {
    return TOKEN_END;
  }

  if (str_eq("RUN", token_str)) {
    return TOKEN_RUN;
  }

  if (str_eq("LIST", token_str)) {
    return TOKEN_LIST;
  }

  syntax_error();
  return TOKEN_SYNTAX;
}

// code[pos, code_len) -> code[pos-offset, code_len-offset),
// code_len -= offset;
void move_left(uint16_t pos, uint16_t offset) {
  uint16_t i;
  for (i = pos; i < code_len; i++) {
    code[i - offset] = code[i];
  }
  code_len -= offset;
}

// code[pos, code_len) -> [pos+offset, code_len+offset),
// code_len += offset;
void move_right(uint16_t pos, uint16_t offset) {
  uint16_t i;

  for (i = code_len + offset - 1; i >= pos + offset; i--) {
    code[i] = code[i - offset];
  }
  code_len += offset;
}

void copy_line(uint16_t pos, uint16_t len) {
  uint16_t i;
  for (i = 0; i < len; i++) {
    code[pos + i] = line_buf[i];
  }
}

uint16_t next_line_pos(uint16_t pos) {
  while (code[pos]) {
    pos++;
  }
  return pos + 1;
}

uint16_t line_num(uint16_t pos) { return code[pos] + (code[pos + 1] << 8); }

uint16_t line_len(uint16_t pos) { return str_len(code + pos + 2) + 3; }

// len 包含 \0
void insert_line(uint16_t line, uint8_t len) {
  uint16_t cpos;
  uint16_t cline;
  uint16_t clen;

  cpos = 0;
  while (1) {
    cline = code[cpos] + (code[cpos + 1] << 8);
    if (cline != 0 && cline < line) {
      cpos += line_len(cpos);
    } else {
      break;
    }
  }

  if (cline == 0) {
    // append at cpos
    copy_line(code_len, len);
    code_len += len;
  } else if (cline == line) {
    clen = line_len(cpos);
    if (len == 3) { // only has line number, delete at cpos
      move_left(cpos + clen, clen);
    } else { // replace at cpos
      if (clen < len) {
        move_right(cpos + clen, len - clen);
      } else if (clen > len) {
        move_left(cpos + clen, clen - len);
      }
      copy_line(cpos, len);
    }
  } else if (cline > line) {
    // insert at cpos;
    move_right(cpos, len);
    copy_line(cpos, len);
  }

  // keep last two bytes as 0
  code[code_len] = 0;
  code[code_len + 1] = 0;
}

void read_line(uint16_t line) {
  uint8_t pos = 0;
  uint8_t token;
  uint8_t *p;

  line_buf[pos++] = (uint8_t)line;
  line_buf[pos++] = (uint8_t)(line >> 8);
  while (1) {
    token = next_token();
    if (token == TOKEN_SYNTAX) {
      return;
    } else {
      // 不包含 \n
      if (token == TOKEN_EOL) {
        line_buf[pos++] = 0;
        insert_line(line, pos);
        return;
      }

      p = token_str;
      while (*p) {
        line_buf[pos++] = *p;
        p++;
      }
      line_buf[pos++] = ' ';
    }
  }
}

// expr3 := VAR|NUM|'(' expr ')'
int16_t eval_expr3() {
  uint8_t token;
  int16_t value;

  if (!running) {
    return 0;
  }

  token = next_token();
  if (token == TOKEN_VAR) {
    return vars[token_str[0] - 'A'];
  } else if (token == TOKEN_NUM) {
    return token_num;
  } else if (token == TOKEN_LP) {
    value = eval_expr();
    token = next_token();
    if (token != TOKEN_RP) {
      syntax_error();
      return 0;
    }
    return value;
  } else {
    syntax_error();
    return 0;
  }
}

// expr2 := ('+'|'-')? expr3
int16_t eval_expr2() {
  uint8_t token;

  if (!running) {
    return 0;
  }

  token = next_token();
  if (token == TOKEN_SUB) {
    return -eval_expr3();
  } else if (token == TOKEN_ADD) {
    return eval_expr3();
  } else {
    last_token = token;
    return eval_expr3();
  }
}

// expr1 := expr2 ('*'|'/' expr2)*
int16_t eval_expr1() {
  uint8_t token;
  int16_t value;

  if (!running) {
    return 0;
  }

  value = eval_expr2();
  while (1) {
    token = next_token();
    if (token == TOKEN_MUL) {
      value *= eval_expr2();
    } else if (token == TOKEN_DIV) {
      value /= eval_expr2();
    } else {
      last_token = token;
      break;
    }
  }
  return value;
}

// expr := expr1 ('+'|'-' expr1)*
int16_t eval_expr() {
  uint8_t token;
  int16_t value;

  if (!running) {
    return 0;
  }

  value = eval_expr1();
  while (1) {
    token = next_token();
    if (token == TOKEN_ADD) {
      value += eval_expr1();
    } else if (token == TOKEN_SUB) {
      value -= eval_expr1();
    } else {
      last_token = token;
      break;
    }
  }
  return value;
}

// print := PRINT STR|expr (',' STR|expr)*
void run_print() {
  uint8_t token;

  while (1) {
    token = next_token();
    if (token == TOKEN_STR) {
      putstr(token_str);
    } else {
      last_token = token;
      putnum(eval_expr());
    }
    token = next_token();
    if (token != TOKEN_COMMA) {
      break;
    }
    putstr(" ");
  }

  putstr("\n");

  if (token != TOKEN_EOL) {
    syntax_error();
  }
}

// input := INPUT VAR
void run_input() {
  uint8_t token;
  uint8_t c;
  uint8_t sign = 1;
  int16_t value;

  token = next_token();
  if (token != TOKEN_VAR) {
    syntax_error();
    return;
  }

  while (1) {
    value = 0;

    io_reset();
    c = io_getchar();
    if (c == '+') {
      sign = 1;
    } else if (c == '-') {
      sign = 0;
    } else if (c >= '0' && c <= '9') {
      do {
        value = value * 10 + c - '0';
        c = io_getchar();
      } while (c >= '0' && c <= '9');

      if (c == '\n') {
        if (sign == 0) {
          value = -value;
        }
        vars[token_num] = value;
        return;
      }
    }
    putstr("[INPUT ERROR]\n");
  }
}

// let := LET VAR = expr
void run_let() {
  uint8_t var;
  uint8_t token = next_token();
  if (token != TOKEN_VAR) {
    syntax_error();
    return;
  }
  var = token_num;
  token = next_token();
  if (token != TOKEN_EQ) {
    syntax_error();
    return;
  }
  vars[var] = eval_expr();
}

// if := IF expr >|>=|<|<=|<>|= expr THEN statement
void run_if() {
  uint8_t token;
  uint8_t cond;
  int16_t value;

  value = eval_expr();
  if (!running) {
    return;
  }

  token = next_token();
  switch (token) {
  case TOKEN_EQ:
    cond = value == eval_expr();
    break;
  case TOKEN_NEQ:
    cond = value != eval_expr();
    break;
  case TOKEN_GE:
    cond = value >= eval_expr();
    break;
  case TOKEN_GT:
    cond = value > eval_expr();
    break;
  case TOKEN_LE:
    cond = value <= eval_expr();
    break;
  case TOKEN_LT:
    cond = value < eval_expr();
    break;
  default:
    syntax_error();
    return;
  }

  if (!running) {
    return;
  }

  token = next_token();
  if (token != TOKEN_THEN) {
    syntax_error();
    return;
  }

  if (cond) {
    run_statement();
  }
}

// goto := GOTO expr
void run_goto() { jmp_line = eval_expr(); }

// gosub := GOSUB expr
void run_gosub() {
  uint16_t pos = next_line_pos(code_pos);
  uint16_t line = line_num(pos);
  if (line == 0) {
    run_error();
    return;
  }
  stack[++tos] = line;
  jmp_line = eval_expr();
}

// return := RETURN
void run_return() {
  if (tos == 0) {
    run_error();
    return;
  }
  jmp_line = stack[tos--];
}

// statement := print|input|let|if|goto|gosub|return|END
void run_statement() {
  uint8_t token = next_token();

  switch (token) {
  case TOKEN_PRINT:
    run_print();
    break;
  case TOKEN_INPUT:
    run_input();
    break;
  case TOKEN_LET:
    run_let();
    break;
  case TOKEN_IF:
    run_if();
    break;
  case TOKEN_GOTO:
    run_goto();
    break;
  case TOKEN_GOSUB:
    run_gosub();
    break;
  case TOKEN_RETURN:
    run_return();
    break;
  case TOKEN_END:
    running = 0;
    return;
  default:
    syntax_error();
  }
}

void run() {
  uint8_t i;

  uint16_t line;

  for (i = 0; i < 26; i++) {
    vars[i] = 0;
  }

  tos = 0;
  running = 1;
  jmp_line = line_num(0);

  while (1) {
    if (!running) {
      return;
    }

    if (jmp_line) {
      code_line = jmp_line;
      jmp_line = 0;

      code_pos = 0;
      while (1) {
        line = line_num(code_pos);

        if (line == 0 || line > code_line) {
          running = 0;
          run_error();
          return;
        } else if (line == code_line) {
          break;
        }
        code_pos += line_len(code_pos);
      }
    } else {
      code_pos = next_line_pos(code_pos); // next line
      code_line = line_num(code_pos);
    }

    code_pos += 2; // skip line number

    last_char = 0;
    last_token = TOKEN_NONE;

    run_statement();
  }
}

void list() {
  uint16_t pos = 0;
  while (pos < code_len) {
    putnum(line_num(pos));
    io_putchar(' ');
    pos += 2;
    while (code[pos]) {
      io_putchar(code[pos]);
      pos++;
    }
    io_putchar('\n');
    pos++; // skip '\0'
  }
}

void main() {
  uint8_t token;
  code[0] = 0;
  code[1] = 0;

  while (1) {
    last_char = 0;
    last_token = TOKEN_NONE;

    io_putchar(':');

    token = next_token();

    if (token == TOKEN_NUM) {
      if (token_num == 0) {
        syntax_error();
      }
      read_line(token_num);
    } else if (token == TOKEN_EOL) {
      // skip
    } else if (token == TOKEN_RUN) {
      token = next_token();
      if (token != TOKEN_EOL) {
        syntax_error();
      } else {
        run();
      }
    } else if (token == TOKEN_LIST) {
      token = next_token();
      if (token != TOKEN_EOL) {
        syntax_error();
      } else {
        list();
      }
    } else if (token == TOKEN_SYNTAX) {
      // do nothing
    } else {
      syntax_error();
    }
  }
}
